package arbitrage;

import java.util.Collection;
import java.util.HashMap;

public class VirtualMachine {
	// Taille de la memoire
	final static int M = 800;
	
	// Taille maximum d'un programme
	final static int MAXPROGLENGTH = 100;
	
	private Inst[] O;
	private int[] A;
	private Mode[] modeA;
	private int [] B;
	private Mode[] modeB;
	
	public VirtualMachine() {
		O = new Inst[M];
		A = new int[M];
		B = new int[M];
		modeA = new Mode[M];
		modeB = new Mode[M];
	}
  
  // On a -2 % 800 donne -2. Cette fonction donne le reste correcte: modulo(-2) = 798.
	static int modulo(int n) {return ((n % M) + M) % M;}
	
   // Charger la commande d'une adresse
	public void setAddress (int position, Inst op, int a, Mode ma, int b, Mode mb) {
		O[position] = op;
		A[position] = a;
		B[position] = b;
		modeA[position] = ma;
		modeB[position] = mb;
	}
	
   // Exécuter le prochain processus
	public void execute(ProcessList listeProc) throws EmptyProcessListException { 
		Integer p = listeProc.poll();
		this.executeAddress(p, listeProc);
		if (listeProc.isEmpty()) throw new EmptyProcessListException();
	}
	
   // Exécuter le processus selon les informations stockées dans son adresse
	public void executeAddress(int position, Collection<Integer> listeProc) {
		int a = 0, b = 0;
		boolean bool;
      
      // On calcule la valeur a.
		switch(modeA[position]) {
			case IM:
				a = 0;
				break;
			case DIR:
				a = A[position];
				break;
			case INDIR:
				a = A[position] + B[modulo(position + A[position])];
				break;
			case DEC:
				B[modulo(position + A[position])]--; 
				a = A[position] + B[modulo(position + A[position])];
				break;
		}
      
      // On calcule la valeur b.
		switch(modeB[position]) {
			case IM:
				b = 0;
				break;
			case DIR:
				b = B[position];
				break;
			case INDIR:
				b = B[position] + B[modulo(position + B[position])];
				break;
			case DEC:
				B[modulo(position + B[position])]--; 
				b = B[position] + B[modulo(position + B[position])];
				break;
		}
      
      // On exécute l'instruction enregistrée.
		switch(O[position]) {
			case DAT:
				break;
			case MOV:
				mov(position, a, modeA[position], b, modeB[position]);
				listeProc.add(modulo(position+1));
				break;
			case ADD:
				add(position, a, modeA[position], b);
				listeProc.add(modulo(position+1));
				break;
			case SUB:
				sub(position, a, modeA[position], b);
				listeProc.add(modulo(position+1));
				break;
			case JMP:
				listeProc.add(modulo(position+a));
				break;
			case JMZ:
				if (B[modulo(position+b)] == 0) listeProc.add(modulo(position+a));
				else listeProc.add((position+1) % M);
				break;
			case JMN:
				if (B[modulo(position+b)] != 0) listeProc.add(modulo(position+a));
				else listeProc.add(modulo(position+1));
				break;
			case CMP:
				bool = cmpTest(position, a, modeA[position], b);
				if (bool) listeProc.add(modulo(position+2));
				else listeProc.add(modulo(position+1));
				break;
			case SLT:
				bool = sltTest(position, a, modeA[position], b);
				if (bool) listeProc.add(modulo(position+2));
				else listeProc.add(modulo(position+1));
				break;
			case DJN:
				bool = djn(position,b, modeB[position]);
				if (bool) listeProc.add(modulo(position+a));
				else listeProc.add(modulo(position+1));
				break;
			case SPL:
				listeProc.add(modulo(position+1));
				listeProc.add(modulo(position+a));
		}
	}

        private void mov(int position, int a, Mode ma, int b, Mode mb) {
                if (ma == Mode.IM) {
                        B[modulo(position+b)] = A[position];
                        modeB[modulo(position+b)] = modeB[position];
                } else {
                        O[modulo(position+b)] = O[modulo(position+a)];
                        A[modulo(position+b)] = A[modulo(position+a)];
                        modeA[modulo(position+b)] = modeA[modulo(position+a)];
                        B[modulo(position+b)] = B[modulo(position+a)];
                        modeB[modulo(position+b)] = modeB[modulo(position+a)];
                }
        }

        private void add(int position, int a, Mode ma, int b) {
                if (ma == Mode.IM) {
                        B[modulo(position + b)] += A[position];
                } else {
                        A[modulo(position + b)] += A[modulo(position+a)];
                        B[modulo(position + b)] += B[modulo(position+a)];
                }
        }

        private void sub(int position, int a, Mode ma, int b) {
                if (ma == Mode.IM) {
                        B[modulo(position + b)] -= A[position];
                } else {
                        A[modulo(position + b)] -= A[modulo(position+a)];
                        B[modulo(position + b)] -= B[modulo(position+a)];
                }
        }

        private boolean cmpTest(int position, int a, Mode ma, int b) {
                if (ma == Mode.IM) {
                        return (A[position] == B[(position+b) % M]);
                }
                else {
                        return ((O[modulo(position+b)] == O[modulo(position+a)]) &&
                                        (A[modulo(position+b)] == A[modulo(position+a)]) && 
                                        (B[modulo(position+b)] == B[modulo(position+a)]));
                }
        }
        
        private boolean sltTest(int position, int a, Mode ma, int b) {
                if (ma == Mode.IM) {
                        return (A[position] < B[modulo(position+b)]);
                } else {
                        return (B[modulo(position+a)] < B[modulo(position+b)]);
                }
        }
        
        private boolean djn(int position, int b, Mode mb) {
                int v;
                if (mb == Mode.IM) {
                        v = B[position];
                } else {
                        B[modulo(position+b)]--;
                        v = B[modulo(position+b)];
                }
                return (v!=0);
        }
        
        public String toString() {
                String r = "----------------------\r\n";
                for (int i = 0 ; i < M ; i++) {
                        r +="("+i+") "+O[i]+"   "+modeA[i]+A[i]+"   "+modeB[i]+B[i]+"\r\n";
                }
                r += "----------------------";
                return r;
        }
        
}
